<?php

namespace ulue\parts\session;


use ulue\parts\session\handler\InterfaceSessionHandler;
use ulue\parts\session\operator\InterfaceSessionOperator;

class Session extends AbstractSession
{

    protected $state;

    protected $data;

    /**
     * @var bool
     */
    protected $debug = false;

    const STATE_ACTIVE      = 'active';     // 激活的
    const STATE_INACTIVE    = 'inactive';   // 暂停的
    const STATE_EXPIRED     = 'expired';    // 过期的
    const STATE_DESTROYED   = 'destroyed';  // 已销毁
    const STATE_ERROR       = 'error';      // 错误

    /**
     * @param InterfaceSessionHandler $handler 定义session处理者
     * @param InterfaceSessionLauncher $launch session 启动管理
     * @param InterfaceSessionOperator $operator session 数据操作
     * @param array $options 选项
     */
    public function __construct(
        InterfaceSessionHandler $handler=null, InterfaceSessionLauncher $launch=null,
        InterfaceSessionOperator $operator=null,   array $options=[]
    ) {
        parent::__construct($handler, $launch, $operator, $options);

        $this->debug = isset($this->options['debug']) ? (bool) $this->options['debug'] : APP_DEBUG;
    }

    /**
     * Start a session.
     * @param   boolean  $restart  Do you want to restart first.
     * @return  boolean
     */
    public function start($restart = false)
    {
        if ($this->state === static::STATE_ACTIVE) {
            return true;
        }

        // Start session if not started
        if ($restart) {

            // 重新生成 session id 替换当前的
            $this->launch->regenerate(true);
        } else {
            $sessionName = $this->launch->getName();
            $cookie      = $this->getCookie();

            // If cookie do not have session id, try to get it from http queries.
            if (empty($cookie[$sessionName]))
            {
                $sessionClean = isset($_GET[$sessionName]) ? $_GET[$sessionName] :  false;

                if ($sessionClean) {

                    $this->launch->getId($sessionClean);
//                    $this->launch->setId($sessionClean);

                    setcookie($sessionName, '', time() - 60*60);
                    $cookie[$sessionName] = '';
                }
            }
        }

        // 开启 session
        $this->launch->start();

//        $this->prepareBagsData($this->bags);

        // 设置激活状态
        $this->state = static::STATE_ACTIVE;

        // Initialise the session
//        $this->setCounter();
//        $this->setTimers();

        // Perform security checks
        $this->validate();

        // 运行启动时，状态已被设置过期
        if (!$restart && $this->state === static::STATE_EXPIRED)
        {
            $this->restart();
        }

        return true;
    }

    public function restart()
    {
        $this->destroy();

        if ($this->state !== static::STATE_DESTROYED)
        {
            throw new \RuntimeException('Session not destroyed, cannot restart.');
        }

        // Re-register the session handler after a session has been destroyed, to avoid PHP bug
        $this->registerHandler();

        return $this->start(true);
    }

    // 销毁 session
    public function destroy()
    {
        // Session was already destroyed
        if ($this->state === static::STATE_DESTROYED) {
            return true;
        }

        /*
         * In order to kill the session altogether, such as to log the user out, the session id
         * must also be unset. If a cookie is used to propagate the session id (default behavior),
         * then the session cookie must be deleted.
         */
        if (isset($_COOKIE[$this->launch->getName()]))
        {
            setcookie( $this->launch->getName(), '', time() - 12*60*60,
                $this->getOption('cookie_path'),
                $this->getOption('cookie_domain')
            );
        }

        $this->launch->destroy();

        $this->state = static::STATE_DESTROYED;

        return true;
    }

    /**
     * 获取
     * @param $key
     * @param null $default
     * @return mixed
     */
    public function get($key, $default=null)
    {
        return $this->operator->get($key) ? : $default;
    }

    /**
     * 获取
     * @param array $keys
     * @return mixed
     */
    public function mget(array $keys)
    {
        return $this->operator->mget($keys);
    }

    public function getAll()
    {
        return $this->operator->all();
    }

    /**
     * 设置
     * @param $key
     * @param $value
     * @return $this
     */
    public function set($key, $value)
    {
        $this->operator->set($key, $value);

        return $this;
    }

    /**
     * 设置
     * @param array $data
     * @return $this
     */
    public function mset(array $data)
    {
        $this->operator->mset($data);

        return $this;
    }

    public function has($key)
    {
        return $this->exists($key);
    }

    public function exists($key)
    {
        if ($this->state !== static::STATE_ACTIVE && $this->state !== static::STATE_EXPIRED) {
            if ($this->debug) {
                throw new \DRuntimeException('Session is not active.');
            }

            return false;
        }

        return $this->operator->exists($key);
    }

    public function remove($key)
    {
        $this->operator->remove($key);

        return $this;
    }

    public function clear()
    {
        $this->operator->clear();

        return $this;
    }

    protected function validate($restart = false)
    {
        // Allow to restart a session
        if ($restart) {

            $this->state = static::STATE_ACTIVE;

            $this->set('session.client.address', null);
            $this->set('session.client.forwarded', null);
            $this->set('session.client.browser', null);
            $this->set('session.token', null);
        }

        // Check if session has expired
        if ($this->getOption('expire_time')) {
            $curTime = $this->get('session.timer.now', 0);
            $maxTime = $this->get('session.timer.last', 0) + ($this->getOption('expire_time') * 60);

            // Empty session variables
            if ($maxTime < $curTime) {
                $this->state = static::STATE_EXPIRED;

                return false;
            }
        }

        // Record proxy forwarded for in the session in case we need it later
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $this->set('session.client.forwarded', $_SERVER['HTTP_X_FORWARDED_FOR']);
        }

        // Check for client address
        if ($this->getOption('fix_address') && isset($_SERVER['REMOTE_ADDR'])) {
            $ip = $this->get('session.client.address');

            if ($ip === null) {
                $this->set('session.client.address', $_SERVER['REMOTE_ADDR']);
            } elseif ($_SERVER['REMOTE_ADDR'] !== $ip) {
                $this->state = static::STATE_ERROR;

                return false;
            }
        }

        // Check for clients browser
        if ($this->getOption('fix_browser', true) && isset($_SERVER['HTTP_USER_AGENT'])) {
            $browser = $this->get('session.client.browser');

            if ($browser === null) {
                $this->set('session.client.browser', $_SERVER['HTTP_USER_AGENT']);
            } elseif ($_SERVER['HTTP_USER_AGENT'] !== $browser) {
                // Nothing to do.
            }
        }

        return true;
    }

    public function registerHandler()
    {
        $this->handler->registerHandler();

        return $this;
    }

    /**
     * Method to get property State
     * @return  string
     */
    public function getState()
    {
        return $this->state;
    }

    /**
     * Method to set property state
     * @param   string $state
     * @return  static  Return self to support chaining.
     */
    public function setState($state)
    {
        $this->state = $state;

        return $this;
    }


    /**
     * Shorthand to check if the session is active
     * @return  boolean
     * @since   2.0
     */
    public function isActive()
    {
        return (bool) ($this->state === static::STATE_ACTIVE);
    }

    public function getIsActive()
    {
        return session_status() == PHP_SESSION_ACTIVE;
    }

    /**
     * Check whether this session is currently created
     *
     * @return  boolean  True on success.
     *
     * @since   2.0
     */
    public function isNew()
    {
        $counter = $this->get('session.counter');

        return (bool) ($counter === 1);
    }


    /**
     * Defined by IteratorAggregate interface
     * Returns an iterator for this object, for use with foreach
     * @return \ArrayIterator
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->getAll());
    }

    /**
     * Checks whether an offset exists in the iterator.
     * @param   mixed  $offset  The array offset.
     * @return  boolean  True if the offset exists, false otherwise.
     */
    public function offsetExists($offset)
    {
        return (boolean) ($this->get($offset) !== null);
    }

    /**
     * Gets an offset in the iterator.
     * @param   mixed  $offset  The array offset.
     * @return  mixed  The array value if it exists, null otherwise.
     */
    public function offsetGet($offset)
    {
        return $this->get($offset);
    }

    /**
     * Sets an offset in the iterator.
     * @param   mixed  $offset  The array offset.
     * @param   mixed  $value   The array value.
     * @return  void
     */
    public function offsetSet($offset, $value)
    {
        $this->set($offset, $value);
    }

    /**
     * Unset an offset in the iterator.
     * @param   mixed  $offset  The array offset.
     * @return  void
     */
    public function offsetUnset($offset)
    {
        $this->set($offset, null);
    }

}
